// Copyright 2007-2008 Rory Plaire (codekaizen@gmail.com)

using System;
using System.Collections.Generic;
using NPack.Interfaces;

namespace NPack
{
    /// <summary>
    /// Provides access to <see cref="Matrix{T}"/> processing operations via a 
    /// configured operations engine.
    /// </summary>
    /// <typeparam name="T">Type of matrix element.</typeparam>
    sealed class MatrixProcessor<T>
        where T : IEquatable<T>, IComparable<T>, IComputable<T>, IConvertible, IFormattable
    {
        private static volatile MatrixProcessor<T> _instance = null;
        private static readonly Object _initSync = new Object();
        private readonly IMatrixOperations<T, IVector<T>, IMatrix<T>> _ops;

        static MatrixProcessor() {}

        private MatrixProcessor(IMatrixOperations<T, IVector<T>, IMatrix<T>> ops)
        {
            _ops = ops;
        }

        /// <summary>
        /// Gets the singleton instance of a MatrixProcessor.
        /// </summary>
        public static MatrixProcessor<T> Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_initSync)
                    {
                        if (_instance == null)
                        {
                            // and this is where the configuration should go...
                            // but we hard code it for now.
                            LinearFactory<T> factory = new LinearFactory<T>();
                            IMatrixOperations<T, IVector<T>, IMatrix<T>> ops
                                = new ClrMatrixOperations<T, IVector<T>, IMatrix<T>>(factory);

                            _instance = new MatrixProcessor<T>(ops);
                        }
                    }
                }

                return _instance;
            }
        }

        /// <summary>
        /// Gets the configured operations engine.
        /// </summary>
        public IMatrixOperations<T, IVector<T>, IMatrix<T>> Operations
        {
            get { return _ops; }
        }

        public static IMatrix<T> Add(IMatrix<T> lhs, IMatrix<T> rhs)
        {
            return Instance.Operations.Add(lhs, rhs);
        }

        public static IVector<T> Add(IVector<T> lhs, IVector<T> rhs)
        {
            return Instance.Operations.Add(lhs, rhs);
        }

        public static IMatrix<T> Subtract(IMatrix<T> lhs, IMatrix<T> rhs)
        {
            return Instance.Operations.Subtract(lhs, rhs);
        }

        public static IVector<T> Subtract(IVector<T> lhs, IVector<T> rhs)
        {
            return Instance.Operations.Subtract(lhs, rhs);
        }

        public static IMatrix<T> Multiply(IMatrix<T> lhs, IMatrix<T> rhs)
        {
            return Instance.Operations.Multiply(lhs, rhs);
        }

        public static IVector<T> Multiply(IMatrix<T> multiplier, IVector<T> columnVector)
        {
            return Instance.Operations.Multiply(multiplier, columnVector);
        }

        public static IVector<T> Multiply(IVector<T> rowVector, IMatrix<T> multiplier)
        {
            return Instance.Operations.Multiply(rowVector, multiplier);
        }

        public static IEnumerable<IMatrix<T>> Multiply(IMatrix<T> lhs, IEnumerable<IMatrix<T>> rhs)
        {
            return Instance.Operations.Multiply(lhs, rhs);
        }

        public static IEnumerable<IMatrix<T>> Multiply(IEnumerable<IMatrix<T>> lhs, IMatrix<T> rhs)
        {
            return Instance.Operations.Multiply(lhs, rhs);
        }

        public static IEnumerable<IVector<T>> Multiply(IMatrix<T> multiplier, IEnumerable<IVector<T>> columnVectors)
        {
            return Instance.Operations.Multiply(multiplier, columnVectors);
        }

        public static IEnumerable<IVector<T>> Multiply(IEnumerable<IVector<T>> rowVectors, IMatrix<T> multiplier)
        {
            return Instance.Operations.Multiply(rowVectors, multiplier);
        }

        public static void Multiply(IMatrix<T> multiplier, IEnumerable<T[]> columnVectorsComponents)
        {
            Instance.Operations.Multiply(multiplier, columnVectorsComponents);
        }

        public static void Multiply(IEnumerable<T[]> rowVectorsComponents, IMatrix<T> multiplier)
        {
            Instance.Operations.Multiply(rowVectorsComponents, multiplier);
        }

        public static void Multiply(IMatrix<T> multiplier, T[] columnVector)
        {
            Instance.Operations.Multiply(multiplier, columnVector);
        }

        public static void Multiply(T[] rowVector, IMatrix<T> multiplier)
        {
            Instance.Operations.Multiply(rowVector, multiplier);
        }

        public static IMatrix<T> ScalarMultiply(IMatrix<T> matrix, T scalar)
        {
            return Instance.Operations.ScalarMultiply(matrix, scalar);
        }

        public static IMatrix<T> Invert(IMatrix<T> matrix)
        {
            return Instance.Operations.Invert(matrix);
        }

        public static IMatrix<T> Negate(IMatrix<T> matrix)
        {
            return Instance.Operations.Negate(matrix);
        }

        public static void Rotate(IMatrix<T> matrix, IVector<T> axis, Double radians)
        {
            Instance.Operations.Rotate(matrix, axis, radians);
        }

        public static void Scale(IMatrix<T> matrix, IVector<T> scaleVector)
        {
            Instance.Operations.Scale(matrix, scaleVector);
        }

        public static void Shear(IMatrix<T> matrix, IVector<T> shearVector)
        {
            Instance.Operations.Shear(matrix, shearVector);
        }

        public static void Translate(IMatrix<T> affineMatrix, IVector<T> translateVector)
        {
            Instance.Operations.Translate(affineMatrix, translateVector);
        }

        public static Double FrobeniusNorm(IMatrix<T> matrix)
        {
            return Instance.Operations.FrobeniusNorm(matrix);
        }

        public static Double OneNorm(IMatrix<T> matrix)
        {
            return Instance.Operations.OneNorm(matrix);
        }

        public static Double TwoNorm(IMatrix<T> matrix)
        {
            return Instance.Operations.TwoNorm(matrix);
        }

        public static Double InfinityNorm(IMatrix<T> matrix)
        {
            return Instance.Operations.InfinityNorm(matrix);
        }

        public static Double Determinant(IMatrix<T> matrix)
        {
            return Instance.Operations.Determinant(matrix);
        }

        public static Int32 Rank(IMatrix<T> matrix)
        {
            return Instance.Operations.Rank(matrix);
        }

        public static Double Condition(IMatrix<T> matrix)
        {
            return Instance.Operations.FrobeniusNorm(matrix);
        }

        public static Double Trace(IMatrix<T> matrix)
        {
            return Instance.Operations.FrobeniusNorm(matrix);
        }

        public static IMatrix<T> Solve(IMatrix<T> a, IMatrix<T> b)
        {
            return Instance.Operations.Solve(a, b);
        }

        public static IMatrix<T> SolveTranspose(IMatrix<T> a, IMatrix<T> b)
        {
            return Instance.Operations.SolveTranspose(a, b);
        }

        public static IMatrix<T> Transpose(IMatrix<T> matrix)
        {
            return Instance.Operations.Transpose(matrix);
        }

        public static void SetMatrix(IMatrix<T> source, IMatrix<T> target)
        {
            Instance.Operations.SetMatrix(source, target);
        }

        public static ILUDecomposition<T, IMatrix<T>> GetLuDecomposition(IMatrix<T> matrix)
        {
            return Instance.Operations.GetLUDecomposition(matrix);
        }

        public static IQRDecomposition<T, IMatrix<T>> GetQrDecomposition(IMatrix<T> matrix)
        {
            return Instance.Operations.GetQRDecomposition(matrix);
        }

        public static ICholeskyDecomposition<T, IMatrix<T>> GetCholeskyDecomposition(IMatrix<T> matrix)
        {
            return Instance.Operations.GetCholeskyDecomposition(matrix);
        }

        public static ISingularValueDecomposition<T, IMatrix<T>> GetSingularValueDecomposition(IMatrix<T> matrix)
        {
            return Instance.Operations.GetSingularValueDecomposition(matrix);
        }

        public static IEigenvalueDecomposition<T, IMatrix<T>> GetEigenvalueDecomposition(IMatrix<T> matrix)
        {
            return Instance.Operations.GetEigenvalueDecomposition(matrix);
        }
    }
}